package ua.yandex.shad.tempseries;

import Jama.*;

public class TemperatureSeriesAnalysis {

    private final double[] temperatureSeries;
    private static final double ABSOLUTE_MINIMAL_TEMPERATURE = -273;
    private static final double MINIMAL_FORECAST_LENGTH = 10;

    private boolean correctSeries() {
        return (temperatureSeries.length != 0);
    }

    public TemperatureSeriesAnalysis(double[] temperatureSeries) throws IllegalTemperatureValueException {

        this.temperatureSeries = new double[temperatureSeries.length];
        for (int i = 0; i < temperatureSeries.length; i++) {
            if (temperatureSeries[i] < ABSOLUTE_MINIMAL_TEMPERATURE) {
                throw new IllegalTemperatureValueException();
            }
            this.temperatureSeries[i] = temperatureSeries[i];
        }
    }

    public double calcAverageTemp() {
        double sum = 0;
        if (correctSeries()) {
            for (double d : temperatureSeries) {
                sum += d / temperatureSeries.length;
            }
        }
        return sum;
    }

    public double calcTempDeviation() {
        double deprivation = 0;
        if (correctSeries()) {
            double average = calcAverageTemp();
            for (double d : temperatureSeries) {
                deprivation += Math.pow(d - average, 2) / temperatureSeries.length;
            }
        }
        return deprivation;
    }

    public double findTempClosestToZero() {
        return findTempClosestToValue(0);
    }

    public double findTempClosestToValue(double tempValue) {
        if (correctSeries()) {
            double rez = temperatureSeries[0];
            for (double d : temperatureSeries) {
                if (Math.abs(d - tempValue) < Math.abs(rez - tempValue)) {
                    rez = d;
                }
                if (Math.abs(d - tempValue) == Math.abs(rez - tempValue)) {
                    rez = (rez > d) ? rez : d;
                }
            }
            return rez;
        } else {
            return 0;
        }
    }

    public double[] findTempsLessThen(double tempValue) {
        int counter = 0;
        for (double d : temperatureSeries) {
            if (d < tempValue) {
                counter++;
            }
        }
        double[] rez = new double[counter];
        counter = 0;
        for (double d : temperatureSeries) {
            if (d < tempValue) {
                rez[counter] = d;
                counter++;
            }
        }
        return rez;
    }

    public double[] findTempsGreaterThen(double tempValue) {
        int counter = 0;
        for (double d : temperatureSeries) {
            if (d >= tempValue) {
                counter++;
            }
        }
        double[] rez = new double[counter];
        counter = 0;
        for (double d : temperatureSeries) {
            if (d >= tempValue) {
                rez[counter] = d;
                counter++;
            }
        }
        return rez;
    }

    public double[] forecastTemp() throws NotEnoughValuesToForecastException {
        if (temperatureSeries.length < MINIMAL_FORECAST_LENGTH) {
            throw new NotEnoughValuesToForecastException();
        } else {
            int size = temperatureSeries.length;
            double[] rez = new double[size + 3];
            for (int i = 0; i < size; i++) {
                rez[i] = temperatureSeries[i];
            }

            int length = size / 2;
            int k = size - length + 1;
            double[][] array = new double[length][];
            for (int i = 0; i < length; i++) {
                array[i] = new double[k];
                for (int j = 0; j < k; j++) {
                    array[i][j] = temperatureSeries[i + j];
                }
            }

            Matrix trajectoryMatrix = new Matrix(array);
            Matrix covarianceMatrix = (trajectoryMatrix.times(trajectoryMatrix.transpose())).times((double) 1 / k);
            Matrix diagonalMatrix = new SingularValueDecomposition(covarianceMatrix).getS();
            Matrix vectorMatrix = new SingularValueDecomposition(covarianceMatrix).getV();
            int eigenValuesCounter = 0;
            while ((eigenValuesCounter < length) && (Math.abs(diagonalMatrix.get(eigenValuesCounter, eigenValuesCounter)) > 0.000001)) {
                eigenValuesCounter++;
            }

            Matrix vBig = vectorMatrix.getMatrix(0, length - 2, 0, eigenValuesCounter - 1);
            Matrix vSmall = vectorMatrix.getMatrix(length - 1, length - 1, 0, eigenValuesCounter - 1);
            Matrix qMatrix = new Matrix(length - 1, 1);
            for (int i = 0; i < length - 1; i++) {
                qMatrix.set(i, 0, temperatureSeries[size - length + 1 + i]);
            }
            rez[size] = (vSmall.times(((((vBig.transpose()).times(vBig)).inverse()).times(vBig.transpose())).times(qMatrix))).get(0, 0);

            for (int i = 0; i < length - 2; i++) {
                qMatrix.set(i, 0, temperatureSeries[size - length + 2 + i]);
            }
            qMatrix.set(length - 2, 0, rez[size]);
            rez[size + 1] = (vSmall.times(((((vBig.transpose()).times(vBig)).inverse()).times(vBig.transpose())).times(qMatrix))).get(0, 0);

            for (int i = 0; i < length - 3; i++) {
                qMatrix.set(i, 0, temperatureSeries[size - length + 3 + i]);
            }
            qMatrix.set(length - 3, 0, rez[size]);
            qMatrix.set(length - 2, 0, rez[size + 1]);
            rez[size + 2] = (vSmall.times(((((vBig.transpose()).times(vBig)).inverse()).times(vBig.transpose())).times(qMatrix))).get(0, 0);

            return rez;
        }
    }
}
